﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using AnjLab.SyncIT.Core.Api;
using AnjLab.SyncIT.Core.Data.Outlook.Calendar;
using AnjLab.SyncIT.Core.Data.Outlook.TaskList;
using Microsoft.Office.Interop.Outlook;

namespace AnjLab.SyncIT.Core.Data.Outlook
{
    public class OutlookTicketDao
    {
        private readonly List<OutlookFolder> folders;

        public OutlookTicketDao() {
            folders = new List<OutlookFolder>();
            folders.AddRange(new OutlookFolder[] {new OutlookTasksFolder(), new OutlookCalendarFolder()});
        }

        private OutlookFolder GetSourceFolder(Source source)
        {
            return folders.Find(f => f.OutlookDestination == source.OutlookDestination);
        }

        public void CreateOrUpdateItem(Source source, ISourceTask task)
        {
            var sourceFolder = GetSourceFolder(source);
            var outlookTask = sourceFolder.FindItem(source.OutlookPrefix, task.ID);

            var isANewTask = false;

            if (!outlookTask.Exists)
            {
                //  This is a new task
                isANewTask = true;

                if (task.IsAssignedToMe(source))
                {
                    outlookTask = sourceFolder.CreateNewItem();
                }
                else
                {
                    //  Skip other's tasks
                    return;
                }
            }

            if (!isANewTask && source.SyncTimestamp < outlookTask.LastModificationTime)
            {
                //  The task has been modified within Outlook
                UpdateSourceTaskFromOutlook(source, task, outlookTask);
            }
            else
            {
                if (isANewTask)
                {
                    //  Initialize new outlook task from source
                    InitializeOutlookTaskFromSource(source, task, outlookTask);
                }
                else
                {
                    //  Update recent data from the source into outlook task
                    UpdateOutlookTaskFromSource(source, task, outlookTask);
                }

                outlookTask.Save();
            }
        }

        public void UpdateSourceTaskFromOutlook(Source source, ISourceTask task, IOutlookItem outlookTask)
        {
            var taskDao = source.GetTaskDao();

            if (!taskDao.SupportsBackSynchronization)
            {
                Library.Log.Debug("The source type \"{0}\" doesn't support back sync", source.ProjectBase);
                return;
            }

            Library.Log.Debug("Outlook task \"{0}\" were modified and may need to be synced with the source backend (taskID = {1})", 
                              outlookTask.Subject, task.ID);

            taskDao.MergeTasks(task, outlookTask);
        }

        private void InitializeOutlookTaskFromSource(Source source, ISourceTask task, IOutlookItem outlookTask)
        {
            //  I may reschedule this task as I want in my Outlook instance after it been added
            if (!outlookTask.SupportsTimeRanging)
                outlookTask.DueDate = DateTime.Today;

            task.InitializeOutlookTask(outlookTask);

            UpdateOutlookTaskFromSource(source, task, outlookTask);
        }

        public void UpdateOutlookTaskFromSource(Source source, ISourceTask task, IOutlookItem outlookTask)
        {
            outlookTask.Importance = ConvertToOutlookImportance(task.Importance);

            outlookTask.Categories = source.OutlookCategory;
            var defaultSubject = string.Format("{0}: {1}", OutlookFolder.GetOutlookItemId(source.OutlookPrefix, task.ID), task.Summary);
            outlookTask.Subject = source.CustomizeSubject
                                      ? ParseTemplate(source.SubjectTemplate, source, task, outlookTask, defaultSubject)
                                      : defaultSubject;
            outlookTask.Body = source.CustomizeBody
                                   ? ParseTemplate(source.BodyTemplate, source, task, outlookTask, outlookTask.Body)
                                   : source.GetProvider().DefaultOutlookItemBodyTemplate;
            // put ID into the body
            outlookTask.Body += "\n--\n  " + OutlookFolder.GetOutlookItemId(source.OutlookPrefix, task.ID);

            //  If the Task has been reassigned or its been closed then 
            //  I can mark it as resolved for me
            if (!task.IsAssignedToMe(source) || task.IsClosed)
            {
                //outlookTask.Complete = true;
                outlookTask.Close();
                return;
            }

            task.UpdateOutlookTask(outlookTask);
        }

        public static OlImportance ConvertToOutlookImportance(Importance importance)
        {
            switch(importance)
            {
                case Importance.High:
                    return OlImportance.olImportanceHigh;
                case Importance.Normal:
                    return OlImportance.olImportanceNormal;
                case Importance.Low:
                    return OlImportance.olImportanceLow;
            }
            throw new InvalidOperationException("Unsupported Importance Level : " + importance);
        }

        public IList<IOutlookItem> FindOpenedItems(Source source)
        {
            return GetSourceFolder(source).FindOpenedItems(source.OutlookPrefix);
        }

        public IList<IOutlookItem> FindModifiedItems(Source source)
        {
            return GetSourceFolder(source).FindModifiedItems(source.SyncTimestamp, source.OutlookPrefix);
        }

        public static string ExtractSummaryFromSubject(string subject)
        {
            return subject.Substring(subject.IndexOf(": ") + ": ".Length);
        }

        public static Importance ConvertFromOutlookImportance(OlImportance olImportance)
        {
            switch(olImportance)
            {
                case OlImportance.olImportanceHigh:
                    return Importance.High;
                case OlImportance.olImportanceLow:
                    return Importance.Low;
                default:
                    return Importance.Normal;
            }

        }

        public string ParseTemplate(string template, Source source, ISourceTask task, IOutlookItem item, string defaultText)
        {
            string result = defaultText;
            if (!string.IsNullOrEmpty(template))
            {
                var temp = ReplaceObjectProperties(task, template);
                temp = ReplaceObjectProperties(source, temp);
                if (!string.IsNullOrEmpty(temp))
                    result = temp;
            }
            return result;
        }

        private static string ReplaceObjectProperties(object obj, string sourceTemplate)
        {
            var objType = obj.GetType();

            MatchEvaluator ev = delegate(Match match)
            {
                var propName = match.Value.Replace("#", "");
                var prop = objType.GetProperty(propName);
                return prop != null ? prop.GetValue(obj, null).ToString() : match.Value;
            };

            return Regex.Replace(sourceTemplate, @"\#[a-z,A-Z,0-9]+\#", new MatchEvaluator(ev));
        }
    }
}